from typing import Union, Sequence, overload

from selenium.webdriver.common.keys import Keys

from common.core import wait
from common.core.api.base import WebdriverApi
from common.core.meta import Xpath
from common.core.globals import p
from common.core.wait import Element
from common.recording import selfmethod_desc
from common.pkg.log import INFO


class ElementApi(WebdriverApi):
    def __get__(self, element_proxy: "ElementActionProxy", owner: type):
        self.locator: Xpath = element_proxy.locator
        return self

    # 设置定位样式
    def _set_located_style(self, element: Element) -> None:
        # 鼠标滚动到元素位置
        if self.scroll_to:
            p.webdriver.execute_script("arguments[0].scrollIntoView(true);", element)
        # 设置 css定位样式
        p.webdriver.execute_script("arguments[0].setAttribute('style', 'border: solid 2px red')", element)

    @overload
    def get_element(self, locator: Xpath) -> Element | Sequence[Element]:
        return Element if not locator.many else Sequence[Element]

    # 获取元素
    def get_element(self, locator):
        # 等待 dom 加载完毕
        wait.DomLoadComplete().wait()
        if not locator.many:
            element = wait.SingleElementEnabled(locator).wait()
            self._set_located_style(element)
            return element
        else:
            elements = wait.MultiElementsEnabled(locator).wait()
            for e in elements:
                self._set_located_style(e)
            return elements


# 获取元素
class GetElement(ElementApi):
    def action(self):
        return self.get_element(self.locator)

    @overload
    def __call__(self, **kwargs) -> Element | Sequence[Element]:
        return Element if not self.locator.many else Sequence[Element]

    @selfmethod_desc("获取元素 | <self.locator.name>")
    def __call__(self, **kwargs):
        self.set_conf(**kwargs)
        return self.run()


# 点击操作
class Click(ElementApi):
    def action(self):
        if isinstance((element := self.get_element(self.locator)), Sequence):
            for el in element:
                try:
                    el.click()
                except Exception as e:
                    INFO.warning(f"因 {str(e)} 异常使用 javascript 执行点击操作")
                    p.execute_script("arguments[0].click()", el)

        else:
            try:
                element.click()
            except Exception as e:
                INFO.warning(f"因 {str(e)} 异常使用 javascript 执行点击操作")
                p.execute_script("arguments[0].click()", element)

    @selfmethod_desc("点击 | <self.locator.name>")
    def __call__(self, **kwargs) -> None:
        self.set_conf(**kwargs)
        return self.run()


# 发送文本操作
class SendKeys(ElementApi):
    def action(self, text: str):
        if isinstance((element := self.get_element(self.locator)), Sequence):
            for el in element:
                el.send_keys(Keys.CONTROL, "A")
                el.send_keys(Keys.BACKSPACE)
                el.send_keys(text)
            return
        element.send_keys(Keys.CONTROL, "A")
        element.send_keys(Keys.BACKSPACE)
        element.send_keys(text)

    @selfmethod_desc("输入框 | <self.locator.name> | <text>")
    def __call__(self, text: str, **kwargs) -> None:
        self.set_conf(**kwargs)
        return self.run(text)


# 获取文本
class Text(ElementApi):
    def action(self):
        if isinstance((element := self.get_element(self.locator)), Sequence):
            return [el.text.strip() for el in element]
        else:
            return element.text.strip()

    @overload
    def __call__(self, **kwargs) -> str | Sequence[str]:
        return str if not self.locator.many else Sequence[str]

    @selfmethod_desc("获取文本 | <self.locator.name>")
    def __call__(self, **kwargs):
        self.set_conf(**kwargs)
        return self.run()


# 获取元素属性操作
class GetAttribute(ElementApi):
    def action(self, attr: str):
        if isinstance((element := self.get_element(self.locator)), Sequence):
            return [el.get_attribute(attr).strip() for el in element]
        else:
            return element.get_attribute(attr).strip()

    @overload
    def __call__(self, **kwargs) -> str | Sequence[str]:
        return str if not self.locator.many else Sequence[str]

    @selfmethod_desc("获取属性 | <self.locator.name> | <attr>")
    def __call__(self, attr: str, **kwargs) -> Union[str, Sequence[str]]:
        self.set_conf(**kwargs)
        return self.run(attr)


# 元素滚动，一些加长的 table，菜单栏等等
class ObjectScrollTo(ElementApi):
    def action(self, x: int, y: int):
        js = f"document.evaluate('{self.locator.xpath_exp}', document).iterateNext().scrollTo({x}, {y})"
        p.execute_script(js)

    @selfmethod_desc("位置滚动 | <self.locator.name> | 滚动到 <[x, y]> 位置")
    def __call__(self, x: int, y: int, **kwargs):
        assert not self.locator.many, f"{self.locator.name} 对象滚动仅支持单一的元素，不能指定 many=True"
        self.set_conf(**kwargs)
        return self.run(x, y)


# 元素查询
class ElementActionProxy(WebdriverApi):
    @selfmethod_desc()
    def __call__(self, locator: Xpath | str):
        self.locator = p.get_locator(locator)
        return self

    get_element = GetElement("获取元素")
    click = Click("点击")
    send_keys = SendKeys("输入文本")
    text = Text("获取文本")
    get_attribute = GetAttribute("获取属性")
    scroll_to = ObjectScrollTo("对象滚动")
